<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**
 * Sticky_auth
 *
 * An authentication library originally built for Stikked.com
 * Built mainly for speed after being disappointed with other authentication libraries
 *
 * Licensed under the New BSD License
 *
 * Loosely based on Redux auth, so it could easily replace an existing implementation of redux_auth
 * However the only code taken from Redux was the password salting
 * Redux auth was written by Mathew Davies available at thepixeldeveloper@googlemail.com
 *
 * @package Sticky_auth
 */

/**
 * The sticky_auth model class
 *
 * @subpackage sticky_auth_model
 */
class Sticky_auth extends Model
{	
	/**
	 * The length of salt used in passwords.
	 *
	 * @var $salt_length
	 * @access protected
	 */
	protected $salt_length = 0;
	
	/**
	 * An array of tables used in sticky
	 *
	 * @var $tables
	 * @access protected
	 */
	protected $tables = array();

	/**
	 * Constructor. Loads sticky_auth config and sets a few initial vars.
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param void
	 */
	public function __construct()
	{
		parent::__construct();
		
		// Load the sticky config
		$this->load->config('sticky_auth_config');
		
		// Set da vars
		$this->tables = $this->config->item('tables');
		
		/*
			Do not change this unless you know what you're doing
			Changing this will prevent existing users from logging in unless their passwords are rehashed
			It also requires the length of the DB field to be updated
		*/
		$this->salt_length = 10;		
	}
	
	// ========================================
	// = Protected Methods for password stuff =
	// ========================================
	/**
	 * Generate a random salt string used in hashing passwords
	 *
	 * @return string
	 * @author Mathew Davies
	 * @access protected
	 *
	 * @param void
	 **/
	protected function generate_salt()
	{
		return substr(md5(uniqid(rand(), true)), 0, $this->salt_length);
	}
	
	/**
	 * Hash and salt a password
	 *
	 * @return string
	 * @author Ben McRedmond
	 * @access protected
	 *
	 * @param string $password The user password we want to hash
	 * @param string|void $salt If comparing against a password already stored in the database you need to provide the salt to get the correct hash: <code>$salt = substr($password, 0, $this->salt_length)</code>
	 **/
	protected function hash_password($password, $salt = null)
	{
		if($salt === null)
		{
			$salt = $this->generate_salt();
		}
		
		$password = $salt . substr(sha1($salt . $password), 0, -$this->salt_length);
		
		return $password;
	}
	
	/**
	 * Compares two passwords if they are the same returns true
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access protected
	 * 
	 * @param string $user_id The user to compare the password with
	 * @param string $password2 The password we want to compare.
	 **/
	protected function compare_password_with_users($user_id, $new_password)
	{
		$this->db->select('password');
		$this->db->where('id', $user_id);
		$query = $this->db->get($this->tables['users']);
		
		$db_password = $query->row_array();
		$db_password = $db_password['password'];
		
		$new_password = $this->hash_password($new_password, substr($db_password, 0, $this->salt_length));
		
		return $db_password == $new_password;
	}
	
	// ============================================
	// = User basics: login, logout, registration =
	// ============================================
	/**
	 * Login a user, compares provided username and password
	 * against the values in the database
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $username The username of the user (Validated)
	 * @param string $password The plain-text password of the user (Validated)
	 **/
	public function login($username, $password)
	{		
		$this->db->select('id, password');
		$this->db->where('username', $username);
		
		$query = $this->db->get($this->tables['users']);
		
		if($query->num_rows() > 0)
		{
			// Get the password stored in the db
			$row = $query->row_array();
			$db_password = $row['password'];
			
			// Generate a hash of the password provided by the user
			$provided_password = $this->hash_password($password, substr($db_password, 0, $this->salt_length));
			
			// Compare them
			if($db_password == $provided_password)
			{
				// Passwords match, set session data and return true.
				$this->session->set_userdata('username', $username);
				$this->session->set_userdata('id', $row['id']);
				
				return true;
			}
			
			// Passwords don't match
			return false;
		}
		
		// No matching username
		return false;
	}
	
	/**
	 * Logout a user
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param void
	 **/
	public function logout()
	{
		// Just get rid of it
		$this->session->sess_destroy();
	}
	
	/**
	 * Register a user
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $username The username (validated)
	 * @param string $password The plain-text password (validated)
	 * @param string $email The user's email address (validated)
	 **/
	public function register($username, $password, $email)
	{
		// The array we'll insert into the users table
		$data = array('username' => $username,
					  'password' => $this->hash_password($password),
					  'email' 	 => $email
					);
		
		$this->db->insert($this->tables['users'], $data);
		
		// The array we'll insert into the meta table
		$data = array('id' => $this->db->insert_id());
		
		$meta_columns = $this->config->item('meta_columns');
		
		// Loop through all the columns we're supposed to create and fill in their default values
		foreach($meta_columns as $column)
		{
			$data[$column['name']] = $column['value'];
		}		
		
		$this->db->insert($this->tables['meta'] ,$data);
	}
	
	// =====================
	// = User Data Methods =
	// =====================
	/**
	 * Get the user data (data stored in the users table opposed to the meta table)
	 *
	 * @return array
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string|int $user_id The user id of the user to get user data for.
	 **/
	public function get_user_data($user_id)
	{
		$this->db->where('id', $user_id);
		$query = $this->db->get($this->tables['users']);
		
		// Return the user data
		return $query->row_array();
	}
	
	/**
	 * Update a users email address
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string|int $user_id The user id of the user whose email we are updating
	 * @param string $email The email address of the user.
	 **/
	public function update_email($user_id, $email)
	{
		$this->db->where('id', $user_id);
		$this->db->update($this->tables['users'], array('email' => $email));
	}
	
	/**
	 * Change a users password, will fail if the old password doesn't match 
	 * the new one
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 * 
	 * @param string|int $user_id The user whose password we are changing.
	 * @param string $old_password The user's old password must be correct or this operation will fail.
	 * @param string $new_password What we are changing the password to.
	 **/
	public function change_password($user_id, $old_password, $new_password)
	{
		if($this->compare_password_with_users($user_id, $old_password))
		{
			$this->db->where('id', $user_id);
			$this->db->update($this->tables['users'], array('password' => $this->hash_password($new_password)));
			
			return true;
		}
		
		return false;
	}
	
	// ==========================
	// = User Meta Data Methods =
	// ==========================
	/**
	 * Gets the meta data for the specified user
	 *
	 * @return array
	 * @author Ben McRedmond
	 * @access public
	 * 
	 * @param string|int $user_id The user id to get meta data for.
	 **/
	public function get_meta($user_id)
	{
		$this->db->where('id', $user_id);
		$query = $this->db->get($this->tables['meta']);
		
		// Return the meta data
		return $query->row_array();
	}
	
	/**
	 * Updates the meta data for the specified user
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string|int $user_id The user id of the user to get meta data for
	 * @param array $data The array of data we want to update the database with. (Does not check if it is an array).
	 **/
	public function update_meta($user_id, $data)
	{
		$this->db->where('id', $user_id);
		$this->db->update($this->tables['meta'], $data);
	}
	
	// ======================
	// = Password Resetting =
	// ======================
	/**
	 * Sends an email basically a wrapper for the email library
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access protected
	 *
	 * @param string $email The email address to send the email to (validated)
	 * @param string $subject The subject of the email we are going to send
	 * @param string $view The view in the email templates directory specified in sticky_auth_config we will use to generate the message
	 * @param array $view_data The optional array of data passed to the view
	 **/
	protected function send_email($email, $subject, $view, $view_data = array())
	{
		$this->load->library('email');
		
		// Get the config stuffs
		$from_address = $this->config->item('email_from_address');
		$from_name = $this->config->item('email_from_name');
		$subject_prefix = $this->config->item('email_subject_prefix');
		
		// Set the email settings
		$this->email->from($from_address, $from_name);
		$this->email->to($email);
		$this->email->subject($subject_prefix . " $subject");
		
		// Set the templates dir
		$view_data['email_templates_directory'] = $this->config->item('email_templates_directory');
		
		// Generate the message
		$message = $this->load->view($view_data['email_templates_directory'].$view, $view_data, true);
		$this->email->message($message);
		
		// Send it
		return $this->email->send();
	}
	
	/**
	 * Start the first step of the forgotten password process. Returns a boolean value signifying email sending success.
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 * 
	 * @param string $email The user email (validated)
	 **/
	public function activate_forgotten_password($email)
	{		
		// Get some data
		$this->db->select('username, forgotten_password_key');
		$this->db->where('email', $email);
		$query = $this->db->get($this->tables['users']);
		
		// Set the username
		$user_data['username'] = $query->row()->username;
		
		// Generate or fill in the password key (if we're sending the email again)
		if($query->row()->forgotten_password_key)
		{
			$user_data['forgotten_password_key'] = $query->row()->forgotten_password_key;
		}
		else
		{
			// Generate the new key
			$user_data['forgotten_password_key'] = sha1(microtime());
			
			// Update the DB with the new key
			$this->db->where('email', $email);
			$this->db->update($this->tables['users'], array('forgotten_password_key' => $user_data['forgotten_password_key']));			
		}
		
		// Send the email and return whether it succeeded or not
		return $this->send_email($email, "Password Reset Request", 'new_password_activate', $user_data);
	}
	
	/**
	 * After the new password has been activated send an email with a new password. Returns a boolean value signifying email sending success.
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $forgotten_password_key A forgotten password key generated in step 1 (validated)
	 **/
	public function send_new_password($forgotten_password_key)
	{
		// Get some data
		$this->db->select('email, username');
		$this->db->where('forgotten_password_key', $forgotten_password_key);
		$query = $this->db->get($this->tables['users']);
		
		// Get the username and email address from our query
		$user_data['username'] = $query->row()->username;
		$email = $query->row()->email;
		
		// Generate a new password
		$user_data['password'] = substr(md5(microtime()), 0, 6);
		$data['password'] = $this->hash_password($user_data['password']);
		
		// Reset the forgotten_password_key to nothing
		$data['forgotten_password_key'] = "";
		
		// Update the database
		$this->db->where('forgotten_password_key', $forgotten_password_key);
		$this->db->update($this->tables['users'], $data);
		
		// Send the email and return whether it succeeded or not
		return $this->send_email($email, "Your new password", 'new_password', $user_data);
	}

	// =================
	// = Login Helpers =
	// =================
	/**
	 * Redirects the user to the login page (by default) if they are not logged in
	 *
	 * @return void
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $redirect Where to redirect the user (/login by default)
	 */
	public function redirect_if_not_logged_in($redirect='login')
	{
		if(!$this->logged_in()) redirect($redirect);
	}
	
	/**
	 * Is the user logged in or not?
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param void
	 **/
	public function logged_in()
	{
		// Return the logged_in bool stored in session data.
		return (boolean) $this->session->userdata('username');
	}
	
	// ==============================================
	// = Methods used for form validation callbacks =
	// ==============================================
	/**
	 * Check if an email is already in use
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $email A valid email address (in the sense user@domain.tld) to check
	 **/
	public function email_exists($email)
	{
		$this->db->select('id');
		$this->db->where('email', $email);
		$query = $this->db->get($this->tables['users']);
		
		return (boolean) $query->num_rows();
	}
	
	/**
	 * Check if a username is already in use
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $username A username to check
	 **/
	public function username_exists($username)
	{
		$this->db->select('id');
		$this->db->where('username', $username);
		$query = $this->db->get($this->tables['users']);
		
		return (boolean) $query->num_rows();
	}
	
	/**
	 * Returns true if the provided key (generated in step 1 of password reset) is valid
	 *
	 * @return boolean
	 * @author Ben McRedmond
	 * @access public
	 *
	 * @param string $forgotten_password_key A forgotten password key generated in step 1 of password reset
	 **/
	public function valid_forgotten_password_key($forgotten_password_key)
	{
		$this->db->select('id');
		$this->db->where('forgotten_password_key', $forgotten_password_key);
		$query = $this->db->get($this->tables['users']);
		
		return (boolean) $query->num_rows();
	}
}